package de.unikoblenz.west.csxpoi;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.LogPrinter;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ZoomButton;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;

import de.unikoblenz.west.csxpoi.R;

/**
 * The main activity displaying an interactive map and an options menu.
 */
public class CsxPoiActivity extends MapActivity {
	
	/**
	 * the location manager for the location thread
	 */
	private LocationManager mLocationManager;

	/**
	 * the location thread listening for location updates
	 */
	private static LocationThread mLocationThread;

	/**
	 * the ID of the user if authorized, else null
	 */
	private static String mUserId = null;

	/**
	 * the constant representing the synchronizing dialog
	 */
	private static final int SYNCHRONIZING_DIALOG = 1;

	/**
	 * the constant representing the search dialog
	 */
	private static final int SEARCH_DIALOG = 2;

	/**
	 * the constant representing the user pois request
	 */
	private static final int USER_POIS = 1;

	/**
	 * the constant representing the all pois request
	 */
	private static final int ALL_POIS = 2;

	/**
	 * the constant representing the menu item for search
	 */
	private static final int MENU_SEARCH = 1;

	/**
	 * the constant representing the menu item for creating new POIs
	 */
	private static final int MENU_NEW_POI = 2;

	/**
	 * the constant representing the menu item for displaying the user's POIs
	 */
	private static final int MENU_MY_POIS = 3;

	/**
	 * the constant representing the menu item for editing the ontology
	 */
	private static final int MENU_EDIT_ONTOLOGY = 4;

	/**
	 * the constant representing the menu item for centering the user's location
	 */
	private static final int MENU_MY_LOCATION = 5;

	/**
	 * the constant representing the menu item for logging in or signing up
	 */
	private static final int MENU_LOGIN = 6;

	/**
	 * The constant representing the menu item for logging out
	 */
	private static final int MENU_LOGOUT = 7;

	/**
	 * the constant representing the group of menu items for authorization
	 */
	private static final int MENU_GROUP_LOGIN_LOGOUT = 1;

	/**
	 * the constant representing the request for the search activity
	 */
	private static final int SEARCH_REQUEST = 1;

	/**
	 * the constant representing the request for the login activity
	 */
	private static final int LOGIN_REQUEST = 2;

	/**
	 * the constant representing the request for the POI editor activity
	 */
	private static final int POI_REQUEST = 3;

	/**
	 * the constant representing the request for the date picker activity
	 */
	private static final int DATE_PICKER_REQUEST = 4;

	/**
	 * {@link TextView} showing the currently chosen date
	 */
	private static Button mDateText;

	/**
	 * Last search query
	 */
	private static String mQuery = "";

	/**
	 * flag indicating that a search has been run and what search type it was
	 */
	private static int mSearchFlag = 0;

	/**
	 * {@link Calendar} that holds the currently chosen date
	 */
	protected static Calendar mCal;

	/**
	 * {@link InteractiveMapView}
	 */
	protected static InteractiveMapView mInteractiveMapView;

	/**
	 * static variable holding this
	 */
	protected static CsxPoiActivity csxPoiActivity;

	/**
	 * Called when the activity is started.
	 */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.map);

		mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
		//load ontologies
		new AsyncOntologyUpdate().execute();

		csxPoiActivity = this;
		
		//init Date button
		mDateText = (Button) findViewById(R.id.textDate);
		mDateText.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				Intent datePickerIntent = new Intent(csxPoiActivity,
						DatePickerActivity.class);
				datePickerIntent.putExtra("year", mCal.get(Calendar.YEAR));
				datePickerIntent.putExtra("month", mCal.get(Calendar.MONTH));
				datePickerIntent.putExtra("day",
						mCal.get(Calendar.DAY_OF_MONTH));
				startActivityForResult(datePickerIntent, DATE_PICKER_REQUEST);
			}
		});
		
		mInteractiveMapView = ((InteractiveMapView) findViewById(R.id.map_view));
		
		// set seekbars invisible
		SeekBar placeholderSeekBar = (SeekBar) findViewById(R.id.placeholderSeekbar);
		placeholderSeekBar.setVisibility(View.INVISIBLE);
		SeekBar dateSeekBar = (SeekBar) findViewById(R.id.dateSeekbar);
		dateSeekBar.setVisibility(View.INVISIBLE);
		ProgressBar searchProgressBar = (ProgressBar) findViewById(R.id.progress_small);
		searchProgressBar.setVisibility(View.INVISIBLE);
		// initial values
		mCal = new GregorianCalendar(TimeZone.getDefault());
		updateDateText();
	}

	/**
	 * animates the map to the current location, if GPS coordinates are provided
	 */
	private void animateMapToCurrentLocation() {
		mLocationThread = new LocationThread(mLocationManager);
		mLocationThread.setPriority(Thread.NORM_PRIORITY);
		mLocationThread.start();

		Location loc = mLocationThread.getLocation();
		if (loc != null)
			mInteractiveMapView.animateTo(loc);
	}

	/**
	 * updates the text view of the currently chosen date
	 */
	protected static void updateDateText() {
		mDateText.setText(mCal.get(Calendar.DAY_OF_MONTH) + "-"
				+ (mCal.get(Calendar.MONTH) + 1) + "-"
				+ mCal.get(Calendar.YEAR));
	}

	/**
	 * updates the overlay of the map
	 */
	protected static void updateMapOverlay() {
		mInteractiveMapView.updateOverlayView();
	}

	/**
	 * Called when the activity is brought back to the front.
	 */
	@Override
	protected void onResume() {

		((InteractiveMapView) findViewById(R.id.map_view)).resume();
		mLocationThread = new LocationThread(mLocationManager);
		mLocationThread.setPriority(Thread.NORM_PRIORITY);
		mLocationThread.start();

		super.onResume();
	}

	/**
	 * Called when the activity is going into the background.
	 */
	@Override
	protected void onPause() {

		((InteractiveMapView) findViewById(R.id.map_view)).pause();
		mLocationThread.requestExitAndWait();
		mLocationThread = null;

		super.onPause();
	}

	/**
	 * Specifies that no route information is displayed on the map.
	 */
	@Override
	protected boolean isRouteDisplayed() {
		return false;
	}

	/**
	 * Called when the options menu is created.
	 */
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		menu.add(Menu.NONE, MENU_SEARCH, 1, R.string.menu_search_text).setIcon(
				android.R.drawable.ic_menu_search);
		menu.add(Menu.NONE, MENU_NEW_POI, 2, R.string.menu_new_poi_text)
				.setIcon(android.R.drawable.ic_menu_add);
		menu.add(Menu.NONE, MENU_MY_POIS, 3, R.string.menu_my_pois_text)
				.setIcon(android.R.drawable.ic_menu_myplaces);
		menu.add(Menu.NONE, MENU_EDIT_ONTOLOGY, 4,
				R.string.menu_edit_ontology_text).setIcon(
				android.R.drawable.ic_menu_manage);
		menu.add(Menu.NONE, MENU_MY_LOCATION, 5, R.string.menu_my_location_text)
				.setIcon(android.R.drawable.ic_menu_mylocation);
		menu.add(MENU_GROUP_LOGIN_LOGOUT, MENU_LOGIN, 6,
				R.string.menu_login_text).setIcon(R.drawable.ic_menu_login);
		return true;
	}

	/**
	 * Called before the options menu is shown.
	 */
	@Override
	public boolean onPrepareOptionsMenu(Menu menu) {
		menu.removeGroup(MENU_GROUP_LOGIN_LOGOUT);
		if (mUserId == null) {
			menu.add(MENU_GROUP_LOGIN_LOGOUT, MENU_LOGIN, 6,
					R.string.menu_login_text).setIcon(R.drawable.ic_menu_login);
			menu.getItem(1).setEnabled(false);
			menu.getItem(2).setEnabled(false);
			menu.getItem(3).setEnabled(false);
		} else {
			menu.add(MENU_GROUP_LOGIN_LOGOUT, MENU_LOGOUT, 6,
					R.string.menu_logout_text)
					.setIcon(R.drawable.ic_menu_login);
			menu.getItem(1).setEnabled(true);
			menu.getItem(2).setEnabled(true);
			menu.getItem(3).setEnabled(true);
		}
		return true;
	}

	/**
	 * Called when an option menu item is selected.
	 */
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
		case MENU_SEARCH:
			showDialog(SEARCH_DIALOG);
			return true;

		case MENU_NEW_POI:
			createPoi(((InteractiveMapView) findViewById(R.id.map_view))
					.getMapCenter());
			return true;

		case MENU_MY_POIS:
			final ArrayList<String> mapBounds = getMapBounds();
			final String left = mapBounds.get(0);
			final String right = mapBounds.get(1);
			final String top = mapBounds.get(2);
			final String bottom = mapBounds.get(3);
			mSearchFlag = USER_POIS;
			mQuery = mUserId;
			new AsyncSearch().execute("" + USER_POIS, mQuery, left, right, top,
					bottom);
			return true;

		case MENU_EDIT_ONTOLOGY:
			Intent ontologyEditorIntent = new Intent(this,
					OntologyEditorActivity.class);
			ontologyEditorIntent.putExtra("user", mUserId);
			startActivity(ontologyEditorIntent);
			return true;

		case MENU_MY_LOCATION:
			centerMyLocation();
			return true;

		case MENU_LOGIN:
			startActivityForResult(new Intent(this, LoginActivity.class),
					LOGIN_REQUEST);
			mSearchFlag = 0;
			mQuery = "";
			return true;

		case MENU_LOGOUT:
			mUserId = null;
			mSearchFlag = 0;
			mQuery = "";
			((InteractiveMapView) findViewById(R.id.map_view)).clearPois();
			Toast.makeText(this, R.string.logged_out_message, Toast.LENGTH_LONG)
					.show();
			return true;
		default:
			return false;
		}
	}

	/**
	 * Called when a dialog is shown.
	 */
	@Override
	protected Dialog onCreateDialog(int id) {
		switch (id) {
		case SYNCHRONIZING_DIALOG:
			ProgressDialog waitAPIDialog = new ProgressDialog(this);
			waitAPIDialog.setTitle(R.string.synchronizing_dialog_title);
			waitAPIDialog.setMessage(getResources().getString(
					R.string.please_wait_text));
			waitAPIDialog.setIndeterminate(true);
			waitAPIDialog.setCancelable(false);
			return waitAPIDialog;

		case SEARCH_DIALOG:
			return getSearchDialog(this);

		}

		return super.onCreateDialog(id);
	}

	/**
	 * Called when an activity returns a result.
	 */
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		Bundle extras = null;
		if (data != null)
			extras = data.getExtras();
		InteractiveMapView mapView = (InteractiveMapView) findViewById(R.id.map_view);
		switch (requestCode) {
		case SEARCH_REQUEST:
			mapView.clearPois();
			if (resultCode == RESULT_OK && extras != null) {
				Object[] parcelables = extras.getParcelableArray("pois");

				PoiWrapper[] pois = new PoiWrapper[parcelables.length];
				if (pois.length > 0) {
					System.arraycopy(parcelables, 0, pois, 0,
							parcelables.length);

					Toast.makeText(this, pois.length + " POIs found",
							Toast.LENGTH_LONG).show();
					mapView.setPois(Arrays.asList(pois));
				} else {
					Toast.makeText(this, R.string.search_error_message,
							Toast.LENGTH_LONG).show();
				}
			}
			break;

		case LOGIN_REQUEST:
			if (resultCode == RESULT_OK && extras != null) {
				mUserId = extras.getString("userid");
				if (mUserId != null) {
					Toast.makeText(this, R.string.login_success_message,
							Toast.LENGTH_LONG).show();
				} else {
					Toast.makeText(this, R.string.login_error_message,
							Toast.LENGTH_LONG).show();
				}
			}
			break;

		case POI_REQUEST:
			if (resultCode == RESULT_OK && extras != null
					&& extras.containsKey("poi")) {
				PoiWrapper poi = extras.getParcelable("poi");
				mapView.updatePoi(poi);
			}
			break;

		case DATE_PICKER_REQUEST:
			if (resultCode == RESULT_OK && extras != null) {
				int year = extras.getInt("year");
				int month = extras.getInt("month");
				int day = extras.getInt("day");

				mCal.set(year, month, day);
				updateDateText();
				updateMapOverlay();
			}
			break;
		}
	}

	/**
	 * Centers the current location on the map.
	 * 
	 * @return the centered current location
	 */
	private Location centerMyLocation() {
		Location location = mLocationThread.getLocation();
		if (location == null) {
			Toast.makeText(this, R.string.location_error_message,
					Toast.LENGTH_LONG).show();
			return null;
		}
		InteractiveMapView mapView = (InteractiveMapView) findViewById(R.id.map_view);
		if (mapView != null) {
			mapView.animateTo(location);
		}
		// TODO: adapt zoom to location accuracy
		return location;
	}

	/**
	 * Opens the POI editor to create a new POI at the specified latitude and
	 * longitude.
	 * 
	 * @param latitude
	 *            the latitude of the new POI
	 * @param longitude
	 *            the longitude of the new POI
	 */
	public void createPoi(double latitude, double longitude) {
		Intent poiEditorIntent = new Intent(this, PoiEditorActivity.class);
		poiEditorIntent.putExtra("latitude", latitude);
		poiEditorIntent.putExtra("longitude", longitude);
		poiEditorIntent.putExtra("user", mUserId);
		startActivityForResult(poiEditorIntent, POI_REQUEST);
	}

	/**
	 * Opens the POI editor to create a new POI at the specified location.
	 * 
	 * @param gp
	 *            the location of the new POI
	 */
	public void createPoi(GeoPoint gp) {
		createPoi(gp.getLatitudeE6() / Constants.MD_FACTOR, gp.getLongitudeE6()
				/ Constants.MD_FACTOR);
	}

	/**
	 * Gets the ID of the user.
	 * 
	 * @return the ID of the user or null if no user is authorized
	 */
	public String getUserId() {
		return mUserId;
	}

	/**
	 * Asynchronously updates the buffered ontology.
	 */
	private class AsyncOntologyUpdate extends AsyncTask<Void, Void, Boolean> {

		/**
		 * Called before the asynchronous update.
		 */
		@Override
		protected void onPreExecute() {
			showDialog(SYNCHRONIZING_DIALOG);
		}

		/**
		 * Called when starting the asynchronous update.
		 */
		@Override
		protected Boolean doInBackground(Void... args) {
			return OntologyBuffer.updateCategories();
		}

		/**
		 * Called after the asynchronous update.
		 */
		@Override
		protected void onPostExecute(Boolean success) {
			dismissDialog(SYNCHRONIZING_DIALOG);
			if (!success) {
				Toast.makeText(CsxPoiActivity.this,
						R.string.server_error_message, Toast.LENGTH_LONG)
						.show();
				finish();
			}
			animateMapToCurrentLocation();
		}
	}

	/**
	 * Inserts Points of interest into the map and display status message
	 * 
	 * @param pois
	 */
	private void insertPOISintoMap(PoiWrapper[] pois) {
		InteractiveMapView mapView = (InteractiveMapView) findViewById(R.id.map_view);
		mapView.clearPois();
		try {
			if (pois != null && pois.length > 0) {
				Toast.makeText(this, pois.length + " POIs found",
						Toast.LENGTH_LONG).show();
				mapView.setPois(Arrays.asList(pois));
			} else {
				Toast.makeText(this, R.string.search_error_message,
						Toast.LENGTH_LONG).show();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Gets a list of bound coordinates of the map part that is displayed at the
	 * moment
	 * 
	 * @return the bounds of the map
	 */
	private ArrayList<String> getMapBounds() {

		ArrayList<String> myList = new ArrayList<String>();
		myList.add("" + mInteractiveMapView.getMapLeft());
		myList.add("" + mInteractiveMapView.getMapRight());
		myList.add("" + mInteractiveMapView.getMapTop());
		myList.add("" + mInteractiveMapView.getMapBottom());
		return myList;
	}

	/**
	 * Creates the POI search dialog
	 * 
	 * @param context
	 * 
	 * @return the search dialog
	 */
	private Dialog getSearchDialog(Context context) {
		final Dialog searchDialog = new Dialog(context);

		final ArrayList<String> mapBounds = getMapBounds();
		final String left = mapBounds.get(0);
		final String right = mapBounds.get(1);
		final String top = mapBounds.get(2);
		final String bottom = mapBounds.get(3);

		searchDialog.setContentView(R.layout.search);

		// Search button:
		final Button searchButton = (Button) searchDialog
				.findViewById(R.id.search_button);
		final EditText queryView = (EditText) searchDialog
				.findViewById(R.id.query);
		searchButton.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				mSearchFlag = ALL_POIS;
				mQuery = queryView.getText().toString().trim();
				new AsyncSearch().execute("" + ALL_POIS, mQuery, left, right,
						top, bottom);
				searchDialog.dismiss();
			}
		});

		// Cancel button:
		final Button cancelButton = (Button) searchDialog
				.findViewById(R.id.cancel_button);

		cancelButton.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				searchDialog.dismiss();
			}
		});

		// All POIs button:
		final Button allPoisButton = (Button) searchDialog
				.findViewById(R.id.all_pois_button);
		allPoisButton.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				mSearchFlag = ALL_POIS;
				mQuery = "";
				new AsyncSearch().execute("" + ALL_POIS, mQuery, left, right,
						top, bottom);
				searchDialog.dismiss();
			}
		});

		return searchDialog;
	}

	/**
	 * Triggers a new search with the actual bound coordinates in case there has
	 * been a search triggered before
	 */
	public void updateSearch() {
		if (mSearchFlag > 0) {

			final ArrayList<String> mapBounds = getMapBounds();
			final String left = mapBounds.get(0);
			final String right = mapBounds.get(1);
			final String top = mapBounds.get(2);
			final String bottom = mapBounds.get(3);

			if (mSearchFlag == ALL_POIS)
				new AsyncSearch().execute("" + ALL_POIS, mQuery, left, right,
						top, bottom);
			else if (mSearchFlag == USER_POIS)
				new AsyncSearch().execute("" + USER_POIS, mQuery, left, right,
						top, bottom);

		}
	}

	/**
	 * Asynchronously searches POIs.
	 */
	private class AsyncSearch extends AsyncTask<String, Void, PoiWrapper[]> {

		/**
		 * Called before the asynchronous search.
		 */
		@Override
		protected void onPreExecute() {
			((ProgressBar) findViewById(R.id.progress_small))
					.setVisibility(View.VISIBLE);
		}

		/**
		 * Called when starting the asynchronous search.
		 */
		@Override
		protected PoiWrapper[] doInBackground(String... args) {
			try {
				URL url = null;
				if (args.length == 6) {
					if (args[0] == "" + USER_POIS) {
						String userId = args[1];
						String left = args[2];
						String right = args[3];
						String top = args[4];
						String bottom = args[5];
						url = new URL(Constants.REST_ADDRESS + "pois"
								+ "?user=" + Encoder.urlEncode(userId)
								+ "&left=" + left + "&right=" + right + "&top="
								+ top + "&bottom=" + bottom);

					} else if (args[0] == "" + ALL_POIS) {
						String query = args[1];
						String left = args[2];
						String right = args[3];
						String top = args[4];
						String bottom = args[5];
						url = new URL(Constants.REST_ADDRESS + "pois"
								+ "?query=" + Encoder.urlEncode(query)
								+ "&left=" + left + "&right=" + right + "&top="
								+ top + "&bottom=" + bottom);
					}

//request pois and parse result
					if (url != null && !isCancelled()) {
						SAXParserFactory spf = SAXParserFactory.newInstance();
						SAXParser sp = spf.newSAXParser();
						XMLReader xr = sp.getXMLReader();
						SearchSaxHandler searchSaxHandler = new SearchSaxHandler();
						xr.setContentHandler(searchSaxHandler);
						xr.parse(new InputSource(url.openStream()));
						List<PoiWrapper> pois = searchSaxHandler.getList();
						
						return pois.toArray(new PoiWrapper[pois.size()]);
					}
				}
			} catch (MalformedURLException e) {
				e.printStackTrace();
			} catch (ParserConfigurationException e) {
				e.printStackTrace();
			} catch (SAXException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			return null;
		}

		/**
		 * Called after the asynchronous search.
		 */
		@Override
		protected void onPostExecute(PoiWrapper[] pois) {
			((ProgressBar) findViewById(R.id.progress_small))
					.setVisibility(View.INVISIBLE);
			if (!isCancelled()) {
				insertPOISintoMap(pois);
			}
		}
	}

	/**
	 * Parses the XML response from the POIs REST request.
	 */
	private class SearchSaxHandler extends DefaultHandler {

		/**
		 * the list of matching POIs
		 */
		private List<PoiWrapper> mPois = new ArrayList<PoiWrapper>();

		/**
		 * the current POI
		 */
		private PoiWrapper mCurrentPoi = null;

		/**
		 * the current category of the current POI
		 */
		private CategoryWrapper mCurrentCategory = null;

		/**
		 * the characters of the current tag
		 */
		private String mCurrentCharacters = "";

		/**
		 * the constant for the category tag
		 */
		private String actualTag = "";

		/**
		 * the constant for the poi tag
		 */
		private static final String POI_TAG = "poi";

		/**
		 * the constant for the type tag
		 */
		private static final String TYPE_TAG = "type";

		/**
		 * the constant for the name tag
		 */
		private static final String NAME_TAG = "name";

		/**
		 * the constant for the lat tag
		 */
		private static final String LAT_TAG = "lat";

		/**
		 * the constant for the long tag
		 */
		private static final String LONG_TAG = "long";

		/**
		 * the constant for the alt tag
		 */
		private static final String ALT_TAG = "alt";

		/**
		 * the constant for the category tag
		 */
		private static final String CATEGORY_TAG = "category";

		/**
		 * the constant for the id tag
		 */
		private static final String ID_ATTRIBUTE = "id";

		/**
		 * the constant for the start tag
		 */
		private static final String START_DATE_TAG = "start";

		/**
		 * the constant for the end tag
		 */
		private static final String END_DATE_TAG = "end";

		/**
		 * Handles opening tags.
		 */
		@Override
		public void startElement(String uri, String localName, String qName,
				Attributes attributes) throws SAXException {

				

			if (localName.equals(POI_TAG)) {
				mCurrentPoi = new PoiWrapper();
				mCurrentPoi.setUri(attributes.getValue(ID_ATTRIBUTE));
			} else if (localName.equals(CATEGORY_TAG)) {
				mCurrentCategory = new CategoryWrapper();
				mCurrentCategory.setUri(attributes.getValue(ID_ATTRIBUTE));
			}
		}

		/**
		 * Handles closing tags.
		 */
		@Override
		public void endElement(String uri, String localName, String qName)
				throws SAXException {
			try {
				

				if (localName.equals(POI_TAG)) {
					mPois.add(mCurrentPoi);
					mCurrentPoi = null;
				} else if (localName.equals(CATEGORY_TAG)) {
					mCurrentPoi.addCategory(mCurrentCategory);
					mCurrentCategory = null;
				}

				if (mCurrentPoi != null) {
					if (mCurrentCategory == null) {
						
						if (localName.equals(NAME_TAG)) {
							mCurrentPoi.setName(mCurrentCharacters);
						} else if (localName.equals(LAT_TAG)) {
							mCurrentPoi.setLatitude(Double
									.valueOf(mCurrentCharacters));
						} else if (localName.equals(LONG_TAG)) {
							mCurrentPoi.setLongitude(Double
									.valueOf(mCurrentCharacters));
						} else if (localName.equals(ALT_TAG)) {
							mCurrentPoi.setAltitude(Double
									.valueOf(mCurrentCharacters));
						} else if (localName.equals(START_DATE_TAG)) {
							mCurrentPoi.setStartDate(mCurrentCharacters);
						} else if (localName.equals(END_DATE_TAG)) {
							mCurrentPoi.setEndDate(mCurrentCharacters);
						} else if (localName.equals(TYPE_TAG)) {
							mCurrentPoi.setType(mCurrentCharacters);
						}
					} else {
						if (localName.equals(NAME_TAG)) {
							mCurrentCategory.setName(mCurrentCharacters);
						}
					}
					mCurrentCharacters = "";
				}
			} catch (Exception e) {
				e.printStackTrace();
				mCurrentCharacters = "";
			}
		}

		/**
		 * Handles characters of tags.
		 */
		@Override
		public void characters(char[] ch, int start, int length)
				throws SAXException {
			for (int i = start; i < start+length; i++) {
				if(ch[i]>31)mCurrentCharacters += ch[i];
			}
//			mCurrentCharacters += new String(ch, start, length);
		}

		/**
		 * Gets the list of matching POIs.
		 * 
		 * @return the list of matching POIs
		 */
		public List<PoiWrapper> getList() {
			return mPois;
		}
	}

}